package cn.lingyangwl.framework.web.handler;

import cn.lingyangwl.framework.core.response.Resp;
import cn.lingyangwl.framework.core.utils.servlet.ServletUtils;
import cn.lingyangwl.framework.tool.core.exception.BaseException;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.*;

/**
 * 全局异常处理,throw new BusinessException(ResultEnum.FAILURE);要在控制层中使用，如果在服务层使用，不会
 * 走BusinessException方法处理，只会走Exception方法进行处理
 * 以下来自stackoverflow
 *
 * @author shenguangyang
 */
@Slf4j
@ControllerAdvice
@RestController
public class GlobalExceptionHandler {

    private void printfLog(Exception e, HttpServletRequest request) {
        if (log.isDebugEnabled()) {
            log.error("exception: {}, class: {}, uri: {}", e.getMessage(), e.getClass().getName(), request.getRequestURI(), e);
        } else {
            log.error("exception: {}, uri: {}", e.getMessage(), request.getRequestURI());
        }
    }

    /**
     * 请求资源不存在异常
     *
     * @param req 请求
     * @param e   异常
     * @return 返回结果
     */
    @ExceptionHandler(value = {NoHandlerFoundException.class})
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Object noHandlerFoundException(HttpServletRequest req, Exception e) {
        printfLog(e, req);
        return Resp.fail("Requested resource does not exist").wrapOfServlet();
    }

    /**
     * 当调用接口时候如果没有传入某个参数就会报出当前异常
     */
    @ExceptionHandler(value = {MissingServletRequestParameterException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object missingServletRequestParameterException(HttpServletRequest req, MissingServletRequestParameterException e) {
        printfLog(e, req);
        return Resp.fail(e.getParameterName() + ": " + e.getMessage()).wrapOfServlet();
    }

    /**
     * 方法参数类型不匹配
     */
    @ExceptionHandler(value = {MethodArgumentTypeMismatchException.class})
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object methodArgumentTypeMismatchException(HttpServletRequest req, Exception e) {
        printfLog(e, req);
        return Resp.fail("Method parameter types do not match").wrapOfServlet();
    }


    /**
     * 参数解析失败
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object httpMessageNotReadableException(HttpServletRequest request, Exception e) {
        printfLog(e, request);
        // 可能是请求体不是json格式
        return Resp.fail("Message not readable").wrapOfServlet();
    }

    /**
     * 处理实体字段校验不通过异常
     * <p>ConstraintViolationException: 普通参数(非 java bean)校验出错时抛出 把校验注解写在参数上</p>
     * <p>MethodArgumentNotValidException：json请求体绑定到java bean上失败时抛出(参数验证失败)</p>
     * <p>BindException：表单提交请求参数绑定到java bean上失败时抛出 这种异常不能在参数对象上加@RequestBody (参数绑定失败)</p>
     * <p>@apiNote MethodArgumentNotValidException 继承 BindException</p>
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({BindException.class, MethodArgumentNotValidException.class})
    public Object bindException(Exception e, HttpServletRequest request) {
        StringBuilder errorMessageSb = new StringBuilder();
        // 错误信息map
        Map<String, String> errorMap = new HashMap<>(16);
        List<ObjectError> allErrors = e instanceof MethodArgumentNotValidException
                ? ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors()
                : ((BindException) e).getAllErrors();
        // 拼接错误信息
        for (ObjectError oe : allErrors) {
            // 获取java bean字段上标注的错误信息
            if (oe instanceof FieldError) {
                String message = String.format("%s: %s", ((FieldError) oe).getField(), oe.getDefaultMessage());
                errorMessageSb.append(message).append("; ");
                errorMap.put(((FieldError) oe).getField(), oe.getDefaultMessage());
            } else {
                errorMap.put(oe.getObjectName(), oe.getDefaultMessage());
            }
        }

        log.error("exception: {}, uri: {}", JSON.toJSONString(errorMap), request.getRequestURI());
        return Resp.fail(errorMessageSb.toString()).wrapOfServlet();
    }

    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object constraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
        printfLog(e, request);
        // 错误信息
        StringBuilder sb = new StringBuilder();
        // 判断异常就是ConstraintViolationException：普通参数(非 java bean)校验出错时抛出 把校验注解写在参数上
        // 遍历校验失败的参数
        for (ConstraintViolation<?> cv : e.getConstraintViolations()) {
            String path = ((PathImpl) cv.getPropertyPath()).getLeafNode().getName();
            String message = String.format("%s: %s", path, cv.getMessage());
            sb.append(message).append("; ");
        }
        return Resp.fail(sb.toString()).wrapOfServlet();
    }

    /**
     * 405 - Method Not Allowed
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    public Object handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e,
                                                               HttpServletRequest request,
                                                               HttpServletResponse response) {
        printfLog(e, request);
        String method = request.getMethod();
        String msg = "Request method " + method + " is not supported, supported methods is " + Arrays.toString(e.getSupportedMethods());
        return Resp.fail(msg).wrapOfServlet();
    }

    /**
     * 415 - Unsupported Media Type
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    public Object handleHttpMediaTypeNotSupportedException(Exception e, HttpServletRequest request) {
        log.error("Unsupported Media Type, exception: {}, url: {}", e.getMessage(), request.getRequestURI());
        return Resp.fail("Unsupported media type").wrapOfServlet();
    }

    /**
     * 处理空指针的异常
     */
    @ExceptionHandler(value = NullPointerException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Object exceptionHandler(HttpServletRequest request, Exception e) {
        printfLog(e, request);
        return Resp.fail("Server error").wrapOfServlet();
    }

    @ExceptionHandler(value = IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Object illegalArgumentException(HttpServletRequest request, Exception e) {
        printfLog(e, request);
        return Resp.fail(e.getMessage()).wrapOfServlet();
    }

    /**
     * 基本异常
     */
    @ExceptionHandler(BaseException.class)
    public Object baseException(BaseException e, HttpServletRequest request) {
        printfLog(e, request);
        if (!e.isShow()) {
            return Resp.ok();
        }
        HttpStatus status = Optional.ofNullable(e.getStatus()).orElse(HttpStatus.BAD_REQUEST);
        ServletUtils.getResponse().setStatus(status.value());
        return Resp.fail(e.getCode(), e.getMessage(), e.getData()).wrapOfServlet();
    }

    /**
     * 声明要捕获的异常
     *
     * @param request 请求体
     * @param e       异常
     * @return 返回的结果
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Object defaultExceptionHandler(Exception e, HttpServletRequest request) {
        printfLog(e, request);
        return Resp.fail("Server error").wrapOfServlet();
    }
}
